//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Linq;
using EffiProzDB.Lib;


namespace EffiProzDB
{

    /**
     * Manages rows involved _in transactions
     *
     * @author fredt@users
     * @version  1.8.0
     * @since 1.8.0
     */
    public class TransactionManager
    {

        public Dictionary<long, int> rowSessionMap;
        public bool reWriteProtect;
        public Database database;

        public TransactionManager(Database db)
        {
            database = db;
            rowSessionMap = new Dictionary<long, int>();
        }

        public void setReWriteProtection(bool value)
        {
            reWriteProtect = value;
        }

        public void checkDelete(Session session, Row row) { }

        public void checkDelete(Session session,
                         HashMappedList<Row, object[]> rowSet)
        {
            if (!reWriteProtect)
            {
                return;
            }

            int sessionid = session.getId();

            foreach (var row in rowSet.Keys)
            {              
                long rowid = row.getId();

                int temp = sessionid;
                rowSessionMap.TryGetValue(rowid, out temp);

                if (temp != sessionid)
                {
                    throw Trace.error(Trace.INVALID_TRANSACTION_STATE_NO_SUBCLASS,
                                      Trace.ITSNS_OVERWRITE);
                }
            }
        }

        public void checkDelete(Session session,
                         List<Row> rowSet)
        {

            if (!reWriteProtect)
            {
                return;
            }

            int sessionid = session.getId();

            foreach (var row in rowSet)
            {             
                long rowid = row.getId();

                int temp = sessionid;
                rowSessionMap.TryGetValue(rowid, out temp);
                if (temp != sessionid)
                {
                    throw Trace.error(Trace.INVALID_TRANSACTION_STATE_NO_SUBCLASS,
                                      Trace.ITSNS_OVERWRITE);
                }
            }
        }

        public void commit(Session session)
        {

            foreach(var tx in session.rowActionList)
            {           
                long rowid = tx.row.getId();

                tx.commit(session);
                rowSessionMap.Remove(rowid);
            }

            session.rowActionList.Clear();
            session.savepoints.Clear();
        }

        public void rollback(Session session)
        {
            lock (this)
            {
                rollbackTransactions(session, 0, false);
                session.savepoints.Clear();
            }
        }

        public void rollbackSavepoint(Session session,
                               string name)
        {           

            if (!session.savepoints.ContainsKey(name))
            {
                throw Trace.error(Trace.SAVEPOINT_NOT_FOUND, name);
            }

            int oi = (int)session.savepoints[name];
            int limit = oi;

            rollbackTransactions(session, limit, false);

            int index = session.savepoints.Keys.IndexOf(name);
        

            while (session.savepoints.Count > index)
            {
                session.savepoints.RemoveAt(session.savepoints.Count - 1);
            }

          
        }

        public void rollbackTransactions(Session session, int limit, bool log)
        {

            Transaction[] list = session.rowActionList.ToArray();
            int size = session.rowActionList.Count;

            for (int i = size - 1; i >= limit; i--)
            {
                Transaction tx = (Transaction)list[i];

                tx.rollback(session, log);
            }

            for (int i = limit; i < size; i++)
            {
                Transaction tx = (Transaction)list[i];
                long rowid = tx.row.getId();

                rowSessionMap.Remove(rowid);
            }          
        }

        public void addTransaction(Session session, Transaction transaction)
        {

            if (reWriteProtect)
            {
                rowSessionMap.Add(transaction.row.getId(), session.getId());
            }
        }

        private long globalActionTimestamp = 0;

        /**
         * gets the next timestamp for an action
         */
        public long nextActionTimestamp()
        {

            globalActionTimestamp++;

            return globalActionTimestamp;
        }

        /**
         * Return an array of all transactions sorted by System Change No.
         */
        public Transaction[] getTransactionList()
        {

            Session[] sessions = database.sessionManager.getAllSessions();
            int[] tIndex = new int[sessions.Length];
            Transaction[] transactions;
            int transactionCount = 0;

            {
                int actioncount = 0;

                for (int i = 0; i < sessions.Length; i++)
                {
                    actioncount += sessions[i].getTransactionSize();
                }

                transactions = new Transaction[actioncount];
            }

            while (true)
            {
                bool found = false;
                long minChangeNo = long.MaxValue;
                int sessionIndex = 0;

                // find the lowest available SCN across all sessions
                for (int i = 0; i < sessions.Length; i++)
                {
                    int tSize = sessions[i].getTransactionSize();

                    if (tIndex[i] < tSize)
                    {
                        Transaction current =
                            (Transaction)sessions[i].rowActionList[( tIndex[i])];

                        if (current.SCN < minChangeNo)
                        {
                            minChangeNo = current.SCN;
                            sessionIndex = i;
                        }

                        found = true;
                    }
                }

                if (!found)
                {
                    break;
                }

                List<Transaction> currentList = sessions[sessionIndex].rowActionList;

                for (; tIndex[sessionIndex] < currentList.Count; )
                {
                    Transaction current =
                        (Transaction)currentList[(tIndex[sessionIndex])];

                    // if the next change no is _in this session, continue adding
                    if (current.SCN == minChangeNo + 1)
                    {
                        minChangeNo++;
                    }

                    if (current.SCN == minChangeNo)
                    {
                        transactions[transactionCount++] = current;

                        tIndex[sessionIndex]++;
                    }
                    else
                    {
                        break;
                    }
                }
            }

            return transactions;
        }

        /**
         * Return a lookup of all transactions ids for cached tables.
         */
        public DoubleIntIndex getTransactionIDList()
        {

            Session[] sessions = database.sessionManager.getAllSessions();
            DoubleIntIndex lookup = new DoubleIntIndex(10, false);

            lookup.setKeysSearchTarget();

            for (int i = 0; i < sessions.Length; i++)
            {
              

                foreach (var tx in sessions[i].rowActionList)
                {    

                    if (tx.tTable.getTableType() == Table.CACHED_TABLE)
                    {
                        lookup.addUnique(tx.row.getPos(), 0);
                    }
                }
            }

            return lookup;
        }

        /**
         * Convert row ID's for cached table rows _in transactions
         */
        public void convertTransactionIDs(DoubleIntIndex lookup)
        {

            Session[] sessions = database.sessionManager.getAllSessions();

            for (int i = 0; i < sessions.Length; i++)
            {           

                foreach (var tx in sessions[i].rowActionList)
                {                 

                    if (tx.tTable.getTableType() == Table.CACHED_TABLE)
                    {
                        int pos = lookup.lookupFirstEqual(tx.row.getPos());

                        tx.row.setPos(pos);
                    }
                }
            }
        }
    }
}
